package com.hexb.smh.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.googlecode.aviator.AviatorEvaluator;
import com.hexb.core.enums.AscColors;
import com.hexb.core.exception.BusinessException;
import com.hexb.core.model.BaseErrorCodes;
import com.hexb.core.utils.Assert;
import com.hexb.core.utils.MapBuilder;
import com.hexb.core.utils.ObjectUtils;
import com.hexb.smh.entity.Commission;
import com.hexb.smh.entity.Config;
import com.hexb.smh.entity.EvaluateHistory;
import com.hexb.smh.entity.EvaluateLevel;
import com.hexb.smh.entity.enums.ConfigType;
import com.hexb.smh.entity.param.config.ExpressionValidParam;
import com.hexb.smh.entity.param.product.ProductQueryParam;
import com.hexb.smh.entity.view.EvaluateResult;
import com.hexb.smh.entity.view.ProductBrandView;
import com.hexb.smh.entity.view.ProductSumView;
import com.hexb.smh.entity.view.ProductView;
import com.hexb.smh.errors.ErrorCodes;
import com.hexb.smh.mapper.*;
import com.hexb.smh.service.IConfigService;
import com.hexb.smh.service.IEvaluateService;
import com.hexb.smh.utils.AccountHelper;
import com.hexb.smh.utils.Converters;
import com.hexb.smh.utils.POIExcelHelper;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.util.CellRangeAddress;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.List;
import java.util.Map;

/**
 * @author : hexb
 */
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@CacheConfig(cacheNames = {"config"})
@Slf4j
public class EvaluateService implements IEvaluateService {

    static private final DecimalFormat f = new DecimalFormat("#.######");

    private final static ObjectMapper MAPPER = new ObjectMapper();

    @NonNull
    final private ConfigMapper configMapper;

    @NonNull
    final private ProductMapper productMapper;

    @NonNull
    final private CommissionMapper commissionMapper;

    @NonNull
    final private EvaluateLevelMapper evaluateLevelMapper;

    @NonNull
    final private EvaluateHistoryMapper evaluateHistoryMapper;

    @NonNull
    private final IConfigService configService;

    @Override
    public EvaluateResult evaluate(Integer[] ids, String dealPrice, Integer brandId) {
        EvaluateHistory h = new EvaluateHistory();
        h.setAccountId(AccountHelper.getAccount().getId());
        Map<ConfigType, Config> map = configService.map();
        //step:(1)获取最少项目限制
        checkLimitItem(ids, map);

        //step:(2)获取公式
        Config expression = map.get(ConfigType.expression);
        Assert.isNull(expression, ErrorCodes.MISS_EXPRESSION);

        //step:(3)获取成本率及折扣率
        //固定成本率=套餐变动成本合计/套餐成交价x100
        //可变成本率=套餐固定成本合计/套餐成交价x100
        //其它成本率=套餐其它成本合计/套餐成交价x100
        //套餐折扣率=套餐成交价/套餐原价合计x100
        ProductSumView sum = productMapper.sum(ids, brandId);
        Assert.isTrue(BigDecimal.ZERO.compareTo(sum.getMarketTotal()) > 0, ErrorCodes.MARKET_PRICE_BY_ZERO);
        Assert.isFalse(new BigDecimal(dealPrice).compareTo(sum.getMarketTotal()) < 0, ErrorCodes.DEAL_PRICE_LARGE_THAN_MARKET_PRICE);

        log.info("{} FIXED:{},VOLATILE:{},OTHER:{},MARKET:{},DEAL:{} {}",
                AscColors.RED,
                sum.getFixedCostTotal(), sum.getVolatileCostTotal(), sum.getOtherCostTotal(), sum.getMarketTotal(),
                dealPrice,
                AscColors.END
        );

        try {
            List<ProductBrandView> pList = productMapper.findWithBrandId(brandId, ids, null, null);
            h.setProductSnapshot(MAPPER.writeValueAsString(pList));
        } catch (JsonProcessingException e) {
            log.error("项目列表转JSON出错");
        }
        h.fill(sum);
        BigDecimal dealDecimal = new BigDecimal(dealPrice);
        h.setDealPrice(dealDecimal);

        //全是小数,不是百分比
        //step:(4)通过折扣率获取销售提成
        //套餐折扣率=套餐成交率/套餐原价合计x100
        BigDecimal comboDiscountRate = sum.getDiscountRate(dealDecimal).multiply(new BigDecimal(10));
        if (comboDiscountRate.compareTo(new BigDecimal(10)) > 0) {
            comboDiscountRate = new BigDecimal(10);
        }

        BigDecimal minDiscount = commissionMapper.minDiscount(brandId);
        //最低折扣不包含
        if (minDiscount != null && comboDiscountRate.compareTo(minDiscount) <= 0) {
            throw new BusinessException(ErrorCodes.DISCOUNT_TOO_LOW);
        }
        //销售提成率
        Commission commissionRate = commissionMapper.getCommissionAmount(comboDiscountRate, dealDecimal, brandId);
        Assert.isNull(commissionRate, ErrorCodes.MISS_COMMISSION, comboDiscountRate, Converters.decimalAmountFormat(dealDecimal));
        BigDecimal commissionRateValue = commissionRate.getAmount().divide(new BigDecimal(100), IConfigService.SCALE, IConfigService.ROUND_TYPE);
        h.setCommissionRate(commissionRateValue);

        Map<String, Object> context = AviatorEvaluator.newEnv(
                "A", sum.getFixedRate(dealDecimal),
                "B", sum.getVolatileRate(dealDecimal),
                "C", sum.getOtherRate(dealDecimal),
                //折扣率=成交价/原价(市场价)
                "D", sum.getDiscountRate(dealDecimal),
                //tag:销售提成成率为百分比,需要转换为小数
                "E", commissionRateValue,
                "F", new BigDecimal(dealPrice));
        log.info("{} FIXED(A):{},VOLATILE(B):{},OTHER(C):{},DISCOUNT(D):{},COMMISSION(E):{},DEAL(F):{} {}",
                AscColors.RED,
                f.format(context.get("A")),
                f.format(context.get("B")),
                f.format(context.get("C")),
                f.format(context.get("D")),
                f.format(context.get("E")),
                f.format(context.get("F")),
                AscColors.END
        );

        log.info("EXPRESSION : {}", expression.getValue());

        Object evaluate = AviatorEvaluator.execute(expression.getValue(), context);
        Assert.isNull(evaluate, ErrorCodes.CALCULATE_FAILED);

        log.info("RESULT : {}", evaluate.toString());

        //tag:评级
        //计算结果分数(匹配为百分比)
        BigDecimal evaluateValue = new BigDecimal(evaluate.toString()).multiply(new BigDecimal(100));
        EvaluateLevel evaluateLevel = evaluateLevelMapper.selectByValue(evaluateValue);
        Assert.isNull(evaluateLevel, ErrorCodes.MISS_EVALUATE_LEVEL, evaluateValue);

        h.fill(evaluateLevel);
        h.setEvaluateScore(evaluateValue);
        evaluateHistoryMapper.save(h);


        return EvaluateResult.builder()
                .passed(evaluateLevel.getPassed())
                .levelName(evaluateLevel.getLevelName())
                .score(f.format(evaluateValue))
                .historyId(h.getId())
                .build();
    }


    private void checkLimitItem(Integer[] ids, Map<ConfigType, Config> map) {
        Config itemLimit = map.getOrDefault(ConfigType.notLessThan, Config.builder().value("5").type(ConfigType.notLessThan).build());
        Assert.isEmpty(ids, ErrorCodes.NOT_ENOUGH_ITEM, itemLimit);
        Assert.isTrue(ids.length < itemLimit.intValue(), ErrorCodes.NOT_ENOUGH_ITEM, itemLimit.intValue());
    }


    @Override
    public EvaluateResult clientEvaluate(Integer[] ids, String dealPrice, Integer brandId) {
        try {
            return evaluate(ids, dealPrice, brandId);
        } catch (BusinessException e) {
            if (ErrorCodes.NOT_ENOUGH_ITEM.getCode().equals(e.getCode())) {
                throw e;
            }
            if (ErrorCodes.DISCOUNT_TOO_LOW.getCode().equals(e.getCode())) {
                throw e;
            }
            throw e;
        } catch (Exception e) {
            throw new BusinessException(ErrorCodes.EVALUATE_FAILED);
        }
    }

    private static final String[] headerTexts = new String[]{
            " 序号 ",
            " 项目名称 ",
            " 项目编码 ",
            " 拼音简码 ",
            "市场价(单位:元)",
            " 产品描述 "
    };

    @Override
    public void exportEvaluateHistory(int id, OutputStream os) {
        EvaluateHistory eh = evaluateHistoryMapper.findById(id);
        Assert.isNull(eh, ErrorCodes.EVALUATE_HISTORY_NOT_FOUND);

        HSSFWorkbook workbook = new HSSFWorkbook();
        HSSFSheet sheet = workbook.createSheet();

        HSSFCellStyle headerStyle = POIExcelHelper.headerStyle(workbook);
        headerStyle.setFillPattern(CellStyle.SOLID_FOREGROUND);
        headerStyle.setFillForegroundColor(HSSFColor.LIME.index);
        HSSFCellStyle style = POIExcelHelper.style(workbook);
        //添加header
        HSSFRow header = sheet.createRow(0);
        for (int i = 0; i < headerTexts.length; i++) {
            HSSFCell cell = header.createCell(i);
            String text = headerTexts[i];
            cell.setCellStyle(headerStyle);
            cell.setCellValue(text);
            sheet.setColumnWidth(i, text.length() * 256);
        }

        ObjectMapper mapper = new ObjectMapper();
        ProductBrandView[] products = null;
        try {
            products = mapper.readValue(eh.getProductSnapshot(), ProductBrandView[].class);
        } catch (JsonProcessingException e) {
            log.error("解析项目快照发生错误,id:" + id);
        }

        //写入项目列表
        int rPosition = 1;
        if (null != products && products.length > 0) {
            for (ProductBrandView p : products) {
                HSSFRow row = sheet.createRow(rPosition++);
                int c = 0;
                writeCellValue(row, c++, style, rPosition + "");
                writeCellValue(row, c++, style, p.getName());
                writeCellValue(row, c++, style, p.getItemNo());
                writeCellValue(row, c++, style, p.getBriefCode());
                writeCellValue(row, c++, style, p.getMarketPrice());
                writeCellValue(row, c, style, p.getDescription());
            }
        }

        rPosition += 2;
        //写入评估结果
        HSSFRow r1 = sheet.createRow(rPosition);
        HSSFCell r1c1 = r1.createCell(0);
        r1c1.setCellStyle(headerStyle);
        r1c1.setCellValue("是否达标");
        CellRangeAddress regionR1C1 = new CellRangeAddress(rPosition, rPosition, 0, 1);
        rangeBorder(regionR1C1, sheet, workbook);
        sheet.addMergedRegion(regionR1C1);

        HSSFCell r1c2 = r1.createCell(2);
        r1c2.setCellStyle(headerStyle);
        r1c2.setCellValue("评估等级");
        CellRangeAddress regionR1C2 = new CellRangeAddress(rPosition, rPosition, 2, 3);
        rangeBorder(regionR1C2, sheet, workbook);
        sheet.addMergedRegion(regionR1C2);

        writeCellValue(r1, 4, headerStyle, "市场价(原价)合计");
        writeCellValue(r1, 5, headerStyle, "成交价");

        //------------------------------------------------------
        rPosition += 1;
        HSSFRow r2 = sheet.createRow(rPosition);
        HSSFCell r2c1 = r2.createCell(0);
        r2c1.setCellValue(eh.getPassed() ? "达标" : "不达标");
        CellRangeAddress regionR2C1 = new CellRangeAddress(rPosition, rPosition, 0, 1);
        rangeBorder(regionR2C1, sheet, workbook);
        sheet.addMergedRegion(regionR2C1);

        HSSFCell r2c2 = r2.createCell(2);
        r2c2.setCellValue(eh.getEvaluateLevel());
        CellRangeAddress regionR2C2 = new CellRangeAddress(rPosition, rPosition, 2, 3);
        rangeBorder(regionR2C2, sheet, workbook);
        sheet.addMergedRegion(regionR2C2);

        writeCellValue(r2, 4, style, f.format(eh.getMarketSum()));
        writeCellValue(r2, 5, style, f.format(eh.getDealPrice()));
        try {
            workbook.write(os);
        } catch (IOException e) {
            log.error("评估结果写入excel文件出错", e);
        }
    }

    private void rangeBorder(CellRangeAddress cellRange, HSSFSheet sheet, HSSFWorkbook workbook) {
        POIExcelHelper.rangeBorder(cellRange, sheet, workbook);
    }

    private void writeCellValue(HSSFRow row, int index, HSSFCellStyle style, String value) {
        HSSFCell cell = row.createCell(index);
        cell.setCellStyle(style);
        cell.setCellValue(value);
    }
}
